package demo.java.jvm;

/**
 * <h1>Java内存模型即Java Memory Model，简称JMM</h1>
 * <p>
 * JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型，所以JMM是隶属于JVM的。
 * Java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。
 * JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看，JMM定义了线程和主内存之间的抽象关系：
 * 线程之间的共享变量存储在主内存（main memory）中，每个线程都有一个私有的本地内存（local memory），
 * 本地内存中存储了该线程以读/写共享变量的副本。 本地内存是JMM的一个抽象概念，并不真实存在。它涵盖了缓存，写缓冲区，寄存器以及其他的硬件和编译器优化。
 * 
 * 
 * 普通对象的结构如下，按64位机器的长度计算
 * <li>1. 对象头(_mark)， 8个字节
 * <li>2. Oop指针，如果是32G内存以下的，默认开启对象指针压缩，4个字节
 * <li>3. 数据区
 * <li>4.Padding(内存对齐)，按照8的倍数对齐
 * 
 * 
 * <p>
 * <br>
 * 数组对象结构是
 * <li>1. 对象头(_mark)， 8个字节
 * <li>2. Oop指针，如果是32G内存以下的，默认开启对象指针压缩，4个字节
 * <li>3. 数组长度，4个字节
 * <li>4. 数据区
 * <li>5. Padding(内存对齐)，按照8的倍数对齐
 *
 */
public interface JavaMemoryModel {
    /**
     * <h2>JVM对Java内存模型的实现</h2>
     * <p>
     * 在JVM内部，Java内存模型把内存分成了两部分：线程栈区和堆区。
     * 
     * JVM中运行的每个线程都拥有自己的线程栈，线程栈包含了当前线程执行的方法调用相关信息，我们也把它称作调用栈。 随着代码的不断执行，调用栈会不断变化。
     * 线程栈还包含了当前方法的所有本地变量信息。一个线程只能读取自己的线程栈，也就是说，线程中的本地变量对其它线程是不可见的。
     * 即使两个线程执行的是同一段代码，它们也会各自在自己的线程栈中创建本地变量，因此，每个线程中的本地变量都会有自己的版本。
     * 所有原始类型(boolean,byte,short,char,int,long,float,double)的本地变量都直接保存在线程栈当中，对于它们的值各个线程之间都是独立的。
     * 对于原始类型的本地变量，一个线程可以传递一个副本给另一个线程，当它们之间是无法共享的。
     * 
     * 堆区包含了Java应用创建的所有对象信息，不管对象是哪个线程创建的，其中的对象包括原始类型的封装类（如Byte、Integer、Long等等）。不管对象是属于一个成员变量还是方法中的本地变量，它都会被存储在堆区。
     * 
     * 一个本地变量如果是原始类型，那么它会被完全存储到栈区。
     * 一个本地变量也有可能是一个对象的引用，这种情况下，这个本地引用会被存储到栈中，但是对象本身仍然存储在堆区。
     * 对于一个对象的成员方法，这些方法中包含本地变量，仍需要存储在栈区，即使它们所属的对象在堆区。
     * 对于一个对象的成员变量，不管它是原始类型还是包装类型，都会被存储到堆区。
     * 
     * Static类型的变量以及类本身相关信息都会随着类本身存储在堆区。
     * 
     * 堆中的对象可以被多线程共享。 如果一个线程获得一个对象的应用，它便可访问这个对象的成员变量。
     * 如果两个线程同时调用了同一个对象的同一个方法，那么这两个线程便可同时访问这个对象的成员变量，但是对于本地变量，每个线程都会拷贝一份到自己的线程栈中。
     * 
     * 
     */
    void JVM对Java内存模型的实现();

    /**
     * <h1>堆</h1> 堆区是JVM中最大一块内存区域，存储着各类生成的对象、数组等，JVM8中把运行时常量池、静态变量也移到堆区进行存储。
     * 堆被用来在运行时分配类实例、数组。不能在栈上存储数组和对象。因为栈帧被设计为创建以后无法调整大小。栈帧只存储指向堆中对象或数组的引用。
     * 与局部变量数组（每个栈帧中的）中的原始类型和引用类型不同，对象总是存储在堆上以便在方法结束时不会被移除。 对象只能由垃圾回收器移除。
     * 为了支持垃圾回收机制，堆被分为了下面三个区域：
     * <li>1、新生代；而年轻代又可分为Eden区、From Survivor、To Survivor三个区域，比例是8:1:1。
     * <li>2、老年代；
     * <li>3、永久代。
     * <p>
     * 根据Java虚拟机规范的规定，Java堆可以处于物理上不连续的内存空间中，只要逻辑上是连续的即可，就像我们的磁盘空间一样。
     * 在实现时，既可以实现成固定大小的，也可以是可扩展的，不过当前主流的虚拟机都是按照可扩展来实现的（通过-Xmx和-Xms控制）。
     */
    void heap堆内存();

    /**
     * 对象和数组永远不会显式回收，而是由垃圾回收器自动回收。通常，过程是这样的：
     * <li>1、新的对象和数组被创建并放入老年代。
     * <li>2、Minor垃圾回收将发生在新生代。依旧存活的对象将从 eden 区移到 survivor 区。
     * <li>3、Major垃圾回收一般会导致应用进程暂停，它将在三个区内移动对象。仍然存活的对象将被从新生代移动到老年代。
     * <li>4、每次进行老年代回收时也会进行永久代回收。它们之中任何一个变满时，都会进行回收。
     */
    void 内存管理();

    /**
     * 
     * 非堆内存指的是那些逻辑上属于 JVM 一部分对象，但实际上不在堆上创建。 非堆内存包括：
     * <li>1、永久代，包括：方法区和驻留字符串（interned strings）；
     * <li>2、代码缓存（Code Cache）：用于编译和存储那些被 JIT 编译器编译成原生代码的方法。
     */
    void 非堆内存();

    /**
     * <h1>即时编译（JIT）</h1>Java字节码是解释执行的，但是没有直接在 JVM 宿主执行原生代码快。 为了提高性能，Oracle Hotspot
     * 虚拟机会找到执行最频繁的字节码片段并把它们编译成原生机器码。编译出的原生机器码被存储在非堆内存的代码缓存中。 通过这种方法，Hotspot
     * 虚拟机将权衡下面两种时间消耗：将字节码编译成本地代码需要的额外时间和解释执行字节码消耗更多的时间。
     */
    void jit即时编译();

    /**
     * 方法区存储了每个类的信息，比如：
     * <li>1、Classloader 引用；
     * <li>2、运行时常量池。包括：数值型常量、字段引用、方法引用和属性；
     * <li>3、字段数据。包括针对每个字段的信息、字段名、类型、修饰符和属性（Attribute）；
     * <li>4、方法数据。包括每个方法、方法名、返回值类型、参数类型（按顺序）、修饰符和属性；
     * <li>5、方法代码。包括每个方法、字节码、操作数栈大小、局部变量大小、局部变量表、异常表、每个异常处理器、开始点、结束点、异常处理代码的程序计数器（PC）偏移量和被捕获的异常类对应的常量池下标。
     * <p>
     * 方法区主要是存储类的元数据的，如虚拟机加载的类信息、编译后的代码等。
     * JDK8之前方法区的实现是被称为一种“永久代”的区域，这部分区域使用JVM内存，但是JDK8的时候便移除了“永久代（Per
     * Gen）”，转而使用“元空间（MetaSpace）”的实现， 而且很大的不同就是元空间不在共用JVM内存，而是使用的系统内存。
     * 所有线程共享同一个方法区，因此访问方法区数据的和动态链接的进程必须线程安全。如果两个线程试图访问一个还未加载的类的字段或方法，必须只加载一次，而且两个线程必须等它加载完毕才能继续执行。
     */
    void 方法区();

    /**
     * 一个编译后的类文件包含下面的结构。可以用 javap 查看编译后的 java class 文件字节码。 如：
     * <li>javap -v -p -s -sysinfo -constants
     * classes/org/jvminternals/SimpleClass.class
     * 
     * <pre>
     *  ClassFile {
    //magic, minor_version, major_version表示类文件的版本信息和用于编译这个类的 JDK 版本。
    u4            magic;
    u2            minor_version;
    u2            major_version;
    //constant_pool类似于符号表，尽管它包含更多数据。下面有更多的详细描述。
    u2            constant_pool_count;
    cp_info       constant_pool[constant_pool_count – 1];
    //access_flags提供这个类的描述符列表。
    u2            access_flags;
    //this_class提供这个类全名的常量池(constant_pool)索引，比如org/jamesdbloom/foo/Bar。
    u2            this_class;
    //super_class提供这个类的父类符号引用的常量池索引。
    u2            super_class;
    //interfaces指向常量池的索引数组，提供那些被实现的接口的符号引用。
    u2            interfaces_count;
    u2            interfaces[interfaces_count];
    //fields提供每个字段完整描述的常量池索引数组。
    u2            fields_count;
    field_info    fields[fields_count];
    //methods指向constant_pool的索引数组，用于表示每个方法签名的完整描述。如果这个方法不是抽象方法也不是 native 方法，那么就会显示这个函数的字节码。
    u2            methods_count;
    method_info   methods[methods_count];
    //attributes不同值的数组，表示这个类的附加信息，包括 RetentionPolicy.CLASS 和 RetentionPolicy.RUNTIME 注解。
    u2            attributes_count;
    attribute_info    attributes[attributes_count];
    }
     * </pre>
     * 
     * <h1>方法</h1>每一个方法包含四个区域：签名、访问标签、字节码、LineNumberTable(为调试器提供源码中的每一行对应的字节码信息)。
     * <h1>字节码操作符：</h1>
     * <li>1、aload0。这个操作码是aload格式操作码中的一个。它们用来把对象引用加载到操作码栈。
     * 表示正在被访问的局部变量数组的位置，但只能是0、1、2、3 中的一个。 还有一些其它类似的操作码用来载入非对象引用的数据，如iload, lload,
     * float 和 dload。其中 i 表示 int，l 表示 long，f 表示 float，d 表示 double。 局部变量数组位置大于 3
     * 的局部变量可以用 iload, lload, float, dload 和 aload 载入。这些操作码都只需要一个操作数，即数组中的位置；
     * <li>2、ldc。这个操作码用来将常量从运行时常量池压栈到操作数栈；
     * <li>3、getstatic。这个操作码用来把一个静态变量从运行时常量池的静态变量列表中压栈到操作数栈。
     * <li>4、invokespecial, invokevirtual。这些操作码属于一组函数调用的操作码，
     * 包括：invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual。
     * invokevirutal指令调用一个对象的实例方法，invokespecial 指令调用实例初始化方法、私有方法、父类方法。
     * <li>5、return。这个操作码属于ireturn、lreturn、freturn、dreturn、areturn 和 return 操作码组。
     * 每个操作码返回一种类型的返回值，其中 i 表示 int，l 表示 long，f 表示 float，d 表示 double，a 表示
     * 对象引用。没有前缀类型字母的 return 表示返回 void。
     * 
     * 
     */
    void 编译后的类文件结构();

    /**
     * <h2>硬件内存架构</h2>
     * <p>
     * 现代计算机一般都有2个以上CPU，而且每个CPU还有可能包含多个核心。因此，如果我们的应用是多线程的话，这些线程可能会在各个CPU核心中并行运行。
     * 
     * 在CPU内部有一组CPU寄存器，也就是CPU的储存器。CPU操作寄存器的速度要比操作计算机主存快的多。在主存和CPU寄存器之间还存在一个CPU缓存，CPU操作CPU缓存的速度快于主存但慢于CPU寄存器。
     * 某些CPU可能有多个缓存层（一级缓存和二级缓存）。计算机的主存也称作RAM，所有的CPU都能够访问主存，而且主存比上面提到的缓存和寄存器大很多。
     * 
     * 当一个CPU需要访问主存时，会先读取一部分主存数据到CPU缓存，进而在读取CPU缓存到寄存器。当CPU需要写数据到主存时，同样会先flush寄存器到CPU缓存，然后再在某些节点把缓存数据flush到主存。
     */
    void 硬件内存架构();

    /**
     * <h2>Java内存模型和硬件架构之间的桥接</h2>
     * <p>
     * Java内存模型和硬件内存架构并不一致。硬件内存架构中并没有区分栈和堆，从硬件上看，不管是栈还是堆，大部分数据都会存到主存中，当然一部分栈和堆的数据也有可能会存到CPU寄存器中。
     * 当对象和变量存储到计算机的各个内存区域时，必然会面临一些问题，其中最主要的两个问题是：
     * <li>1. 共享对象对各个线程的可见性
     * <p>
     * 当多个线程同时操作同一个共享对象时，如果没有合理的使用volatile和synchronization关键字，一个线程对共享对象的更新有可能导致其它线程不可见。
     * 想象一下我们的共享对象存储在主存，一个CPU中的线程读取主存数据到CPU缓存，然后对共享对象做了更改，但CPU缓存中的更改后的对象还没有flush到主存，
     * 此时线程对共享对象的更改对其它CPU中的线程是不可见的。最终就是每个线程最终都会拷贝共享对象，而且拷贝的对象位于不同的CPU缓存中。
     * 要解决共享对象可见性这个问题，我们可以使用java volatile关键字。
     * volatile关键字可以保证变量会直接从主存读取，而对变量的更新也会直接写到主存。volatile原理是基于CPU内存屏障指令实现的。
     * 
     * <li>2. 共享对象的竞争现象
     * <p>
     * 如果多个线程共享一个对象，如果它们同时修改这个共享对象，这就产生了竞争现象。要解决上面的问题我们可以使用java synchronized代码块。
     * synchronized代码块可以保证同一个时刻只能有一个线程进入代码竞争区，synchronized代码块也能保证代码块中所有变量都将会从主存中读，当线程退出代码块时，对所有变量的更新将会flush到主存，不管这些变量是不是volatile类型的。
     * 
     */
    void Java内存模型和硬件架构之间的桥接();

    /**
     * 内存屏障（memory Barriers），指令重排序，happens-before规则，as-if-serial语义。
     * 
     * 
     */
    void 支撑Java内存模型的基础原理();

    /**
     * <h1>Memory Barrier（内存屏障）</h1>内存屏障，又称内存栅栏，是一个CPU指令，基本上它是一条这样的指令：
     * <li>1、保证特定操作的执行顺序。
     * <li>2、影响某些数据（或则是某条指令的执行结果）的内存可见性。
     * <p>
     * <br>
     * 编译器和CPU能够重排序指令，保证最终相同的结果，尝试优化性能。插入一条Memory
     * Barrier会告诉编译器和CPU：不管什么指令都不能和这条Memory Barrier指令重排序。
     * Memory_Barrier所做的另外一件事是强制刷出各种CPU cache，如一个 Write-Barrier（写入屏障）将刷出所有在 Barrier
     * 之前写入 cache 的数据，因此，任何CPU上的线程都能读取到这些数据的最新版本。java的volatile是基于Memory_Barrier实现的。
     * 如果一个变量是volatile修饰的，JMM会在写入这个字段之后插进一个Write-Barrier指令，并在读这个字段之前插入一个Read-Barrier指令。
     * 
     * 这意味着，如果写入一个volatile变量，就可以保证：
     * <li>1、一个线程写入变量a后，任何线程访问该变量都会拿到最新值。
     * <li>2、在写入变量a之前的写入操作，其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入。
     * 
     */
    void memoryBarrier();

    /**
     * <h1>happens-before规则</h1>
     * 从jdk5开始，java使用新的JSR-133内存模型，基于happens-before的概念来阐述操作之间的内存可见性。
     * 在JMM中，如果一个操作的执行结果需要对另一个操作可见，那么这两个操作之间必须要存在happens-before关系，这个的两个操作既可以在同一个线程，也可以在不同的两个线程中。
     * 与程序员密切相关的happens-before规则如下：
     * <ol>
     * <li>1、程序顺序规则：一个线程中的每个操作，happens-before于该线程中任意的后续操作。
     * <li>2、监视器锁规则：对一个锁的解锁操作，happens-before于随后对这个锁的加锁操作。
     * <li>3、volatile域规则：对一个volatile域的写操作，happens-before于任意线程后续对这个volatile域的读。
     * <li>4、传递性规则：如果 A happens-before B，且 B happens-before C，那么A happens-before C。
     * </ol>
     * 注意：两个操作之间具有happens-before关系，并不意味前一个操作必须要在后一个操作之前执行！仅仅要求前一个操作的执行结果，对于后一个操作是可见的，且前一个操作按顺序排在后一个操作之前。
     */
    void happensBefore();

    /**
     * <h1>指令重排序</h1> 在执行程序时，为了提高性能，编译器和处理器会对指令做重排序。但是，JMM确保在不同的编译器和不同的处理器平台之上，
     * 通过插入特定类型的Memory_Barrier来禁止特定类型的编译器重排序和处理器重排序，为上层提供一致的内存可见性保证。
     * <li>编译器优化重排序：编译器在不改变单线程程序语义的前提下，可以重新安排语句的执行顺序。
     * <li>指令级并行的重排序：如果不存在数据依赖性，处理器可以改变语句对应机器指令的执行顺序。
     * <li>内存系统的重排序：处理器使用缓存和读写缓冲区，这使得加载和存储操作看上去可能是在乱序执行。
     */
    void 指令重排序();

    /**
     * <h1>数据依赖性</h1>如果两个操作访问同一个变量，其中一个为写操作，此时这两个操作之间存在数据依赖性。
     * 编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序，即不会重排序。
     */
    void 数据依赖性();

    /**
     * <h3>as-if-serial</h3>
     * 不管怎么重排序，单线程下的执行结果不能被改变，编译器、runtime和处理器都必须遵守as-if-serial语义。
     */
    void asIfSerial();

    /**
     * <h1>运行时常量池</h1>JVM 维护了一个按类型区分的常量池，一个类似于符号表的运行时数据结构。尽管它包含更多数据。Java字节码需要数据。
     * 这个数据经常因为太大不能直接存储在字节码中，取而代之的是存储在常量池中，字节码包含这个常量池的引用。
     * 运行时常量池被用来动态链接。常量池中可以存储多种类型的数据：1、数字型；2、字符串型；3、类引用型；4、域引用型；5、方法引用；
     * <p>
     * 简单来讲class文件在编译后除了存储一些类的版本、字段、方法、接口等元数据信息外，
     * 还有一部分信息是常量池，这个常量池我们称之为“静态常量池”，只是作为一种持久化数据存储在硬盘上，代表编译期生成的各种字面量和符号引用（最常见的就是字符串常量），
     * 那么这类信息被加载到内存中就会以运行时常量池的形式存在内存中，JDK7以前这类信息被存储在方法区，但是JDK8都已经移到了堆区。
     * 这类数据变量的好处简单来说就是如果堆区中已经存在一个数据变量，即使再创建一个这样的变量，那么JVM将会直接指向已经创建好的数据，而不会再分配内存区域，
     * 这样一方面加快数据的创建，另一方面节省内存空间！但是实际上的机制要复杂一些，可以参考我之前讲述的String类去理解！
     */
    void 运行时常量池();

    /**
     * 在Java里面创建对象有几种方式,这三种方式本质上都是一样的，都是常规的Java创建对象的new机制，不管是显式地还是隐式的。
     * <li>1. 显式地调用new语句， 比如 DemoClass demo = new DemoClass()
     * <li>2. 利用反射机制，通过Class对象的newInstance()方法，比如DemoClass demo =
     * DemoClass.class.newInstance()。 但是有个前提就是必须提供无参的构造函数
     * <li>3. 利用反射机制，利用Constructor对象来创建对象
     * <p>
     * <br>
     * new机制有个问题就是: 当类只提供有参的构造函数时，必须使用这个有参的构造函数。
     * <p>
     * Object foo = new Object();写成字节码将是下面这样：
     * 
     * <pre>
     0:    new #2            // Class java/lang/Object
     1:    dup
     2:    invokespecial #3  // Method java/ lang/Object "<init>"( ) V
     * </pre>
     * 
     * <li>new 操作码的后面紧跟着操作数 #2 。这个操作数是常量池的一个索引，表示它指向常量池的第二个实体。 第二个实体是一个类的引用，
     * 这个实体反过来引用了另一个在常量池中包含 UTF8编码的字符串类名的实体（//Class java/lang/Object）。
     * 然后，这个符号引用被用来寻找 java.lang.Object 类。 new操作码创建一个类实例并初始化变量。新类实例的引用则被添加到操作数栈。
     * 
     * <li>dup操作码创建一个操作数栈顶元素引用的额外拷贝。
     * 
     * <li>最后用 invokespecial来调用第 2行的实例初始化方法。
     * 操作码也包含一个指向常量池的引用。初始化方法把操作数栈出栈的顶部引用当做此方法的一个参数。 最后这个新对象只有一个引用，这个对象已经完成了创建及初始化。
     */
    void newObject();

    /**
     * 异常表像这样存储每个异常处理信息：
     * <li>1、起始点（Start point）；
     * <li>2、结束点（End point）；
     * <li>3、异常处理代码的 PC 偏移量；
     * <li>4、被捕获异常的常量池索引。
     * <p>
     * <br>
     * 如果一个方法有定义 try-catch 或者 try-finally 异常处理器，那么就会创建一个异常表。 它为每个异常处理器和
     * finally代码块存储必要的信息，包括处理器覆盖的代码块区域和处理异常的类型。 当方法抛出异常时，JVM
     * 会寻找匹配的异常处理器。如果没有找到，那么方法会立即结束并弹出当前栈帧，这个异常会被重新抛到调用这个方法的方法中（在新的栈帧中）。
     * 如果所有的栈帧都被弹出还没有找到匹配的异常处理器，那么这个线程就会终止。如果这个异常在最后一个非守护进程抛出（比如这个线程是主线程），那么也有会导致
     * JVM 进程终止。 Finally异常处理器匹配所有的异常类型，且不管什么异常抛出 finally 代码块都会执行。
     * 在这种情况下，当没有异常抛出时，finally代码块还是会在方法最后执行。 这种靠在代码 return之前跳转到finally代码块来实现。
     */
    void 异常表();

    /**
     * 除了按类型来分的运行时常量池，Hotspot JVM 在永久代还包含一个符号表。这个符号表是一个哈希表，保存了符号指针到符号的映射关系（也就是
     * Hashtable）， 它拥有指向所有符号（包括在每个类运行时常量池中的符号）的指针。
     * 
     * 引用计数被用来控制一个符号从符号表从移除的过程。比如当一个类被卸载时，它拥有的在常量池中所有符号的引用计数将减少。当符号表中的符号引用计数为 0
     * 时，符号表会认为这个符号不再被引用，将从符号表中卸载。 符号表和后面介绍的字符串表都被保存在一个规范化的结构中，以便提高效率并保证每个实例只出现一次。
     */
    void 符号表();

    /**
     * <h1>永久代</h1> “方法区（Method Area）”是JVM规范中的概念，不同的Java版本有不同的实现。
     * 方法区为所有线程共享。主要用于存储类的信息、常量池、方法数据、方法代码等。 方法区逻辑上属于堆的一部分，但是为了与堆进行区分，通常又叫“非堆”。
     * 
     * <li>Java6中的“方法区”实现与规范相同，称“永久代（Permanent Generation）”，
     * <li>Java7中“方法区”也称为“永久代（Permanent_Generation）”但是“方法区”中的“常量池”移动到了“堆”中（因此String.intern()方法在Java6和7有区别），
     * <li>Java8中“方法区”称为“元空间（Meta Space）”并且不在虚拟机中而在本地内存中（JVM参数也不同）
     */
    void permanentGeneration();
}
